package org.example.judge.oj;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.example.judge.service.ProblemCaseService;
import org.example.judge.service.ProblemService;
import org.example.judge.service.SubmitService;
import org.example.judge_common.entity.PageResult;
import org.example.judge_common.entity.ResponseResult;
import org.example.judge_common.hander.RetrunMsgException;
import org.example.judge_common.util.CompileUnit;
import org.example.judge_common.util.MyUtils;
import org.example.judge_feign_api.ContestClient;
import org.example.judge_pojo.ContestRecord;
import org.example.judge_pojo.Problem;
import org.example.judge_pojo.ProblemCase;
import org.example.judge_pojo.Submit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.example.judge_common.properties.JudgeProperties;
import javax.annotation.PostConstruct;
import java.io.*;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class JudgeListener {

    @Autowired
    private GoJudgeDruid goJudgeDruid;

    @Qualifier("ProblemServiceImpl")
    @Autowired
    private ProblemService problemService;

    @Qualifier("ProblemCaseServiceImpl")
    @Autowired
    private ProblemCaseService problemCaseService;
    @Autowired
    private RedisTemplate redisTemplate;

    @Qualifier("SubmitServiceImpl")
    @Autowired
    private SubmitService submitService;
    @Autowired
    private JudgeProperties judgeProperties;

    @Autowired
    private ContestClient contestClient;
    @PostConstruct
    public void start() {
        log.info("启动判题机监视器。。。");
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                JudgeListener.this.run();
            }
        };
        Thread thread = new Thread(runnable);
        thread.setDaemon(true);
        thread.start();
        log.info("判题机监视器启动完成");
    }

    //判题机监听入口
//    @Async
    public void run() {
//        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(goJudgeDruid.getSum(), goJudgeDruid.getSum(), 2, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(goJudgeDruid.getSum()));
        while (true) {

            String id = (String) redisTemplate.opsForList().rightPop("submit");
            if(id==null) {  //没有要评测的代码
                try {
                    Thread.sleep(800);
                    continue;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("获取到要进行评测的代码Id：{},开始申请评测机",id);
            GoJudge goJudge = goJudgeDruid.getGoJudge();  //申请评测机
            if (goJudge != null) {
                //获取提交的详细信息
                Object o = redisTemplate.opsForValue().get(id);
                Submit submit  = null;
                if (o==null){  //超过五分钟，提交超时
                    log.warn("提交的{}的详细信息为空，可能提交超时",id);
                    submit = submitService.getById(id);
                }else submit  = JSON.parseObject(((String) o),Submit.class);


                Submit finalSubmit = submit;
                Runnable runnable = new Runnable() { //开启异步线程
                    @Override
                    public void run() {
                        try{
                                JudgeListener.this.run(goJudge, finalSubmit);

                        }catch (Exception e){
                            e.printStackTrace();
                            log.error("发生评测异常，释放评测机");

                            //保存错误信息
                            if(finalSubmit !=null){
                                finalSubmit.setStatus(-1);
                                finalSubmit.setErrorStatus(JudgeResult.INNER_ERROR); //内部错误
                                submitService.updateById(finalSubmit);
                            }

                        }
                    }
                };
                Thread thread = new Thread(runnable);
                thread.setDaemon(true);
                thread.start();

            } else {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }




    /**
     * 运行
     *
     * @param goJudge
     * @param submit
     */
//    @Async
    public void run(GoJudge goJudge, Submit submit) throws InterruptedException, RetrunMsgException {
        //获取题目
        Problem problem = problemService.getById(submit.getProblemId());
        GoJudge goJudge1 = null;
        if(problem.getMode()==1){  //交互判题，再申请一台评测机
            log.debug("此题目MODE==1,为交互判题，需要申请额外的评测机");
            if(goJudgeDruid.getSum()<2){  //获取评测机总数量，不可能申请到额外的
                //直接内部错误

                submit.setErrorMsg("系统不支持交互判题方式");
                throw new RetrunMsgException("评测机总数不足两个，无法完成交互判题");
            }
            goJudge1 = goJudgeDruid.getGoJudge();
            while(goJudge1==null){  //循环获取
                Thread.sleep(200);
                goJudge1 = goJudgeDruid.getGoJudge();
            }

            log.debug("交互判题评测机申请完成");

        }
        try{   //保证释放评测机
            //获取测试数据
            List<ProblemCase> problemCaseList = problemCaseService.getSubmitCasesByProblemId(problem.getId());
            //同步到redis
            submit.setStatus(3);
            submit.setTime(0);
            submit.setMemory(0);
            redisTemplate.opsForValue().set(submit.getId(), JSON.toJSONString(submit));
            for (ProblemCase problemCase : problemCaseList) {
                JudgeResult run = null;
                try {
                    if (problemCase.getType() == 1) {   //文件样例
                        InputStream input = this.getFileInput(problemCase);
                        InputStream output = this.getFileOutput(problemCase);
                        log.debug("判题:{}开始：inputFile:{}.in,outputFile:{}.out",submit.getId(),problemCase.getInputFileId(),problemCase.getOutputFileId());

                        if(problem.getMode()==1){
                            run = this.runSpj(goJudge,goJudge1,submit,problem,input);
                        }else{
                            run = goJudge.run(input, output, submit.getCode(),
                                    problem.getTimeLimit().longValue(), problem.getMemoryLimit().longValue(),
                                    CompileUnit.get(submit.getLanguage()));
                        }




                    }
                } catch (Exception e) {
                    log.error("可能测试数据文件打不开：case:{},error:{}", problemCase, e.getMessage());
                    // 错误信息保存
                    submit.setStatus(-1);
                    submit.setErrorMsg(e.getMessage());
                    break; //退出判题
                }
                //数据判例
                if (problemCase.getType() != 1) {
                    log.debug("判题:{}开始：input:{},output:{}",submit.getId(),problemCase.getInput(),problemCase.getOutput());

                    try {
                        if(problem.getMode()==1){

                            run = this.runSpj(goJudge,goJudge1,submit,problem,problemCase.getInput());
                        }else{
                            run = goJudge.run(problemCase.getInput(), problemCase.getOutput(), submit.getCode(),
                                    problem.getTimeLimit().longValue(), problem.getMemoryLimit().longValue(),
                                    CompileUnit.get(submit.getLanguage()));
                        }

                    } catch (Exception e) {
                        log.error("判题机错误：error：{}", e.getMessage());
//                    e.printStackTrace();
                        // 错误信息保存
                        submit.setStatus(-1);
                        submit.setErrorStatus(6); //记录运行结果信息
                        submit.setErrorMsg(e.getMessage());
//                    submit.setErrorMsg(e.getMessage());
                        break; //退出判题
                    }
                }
                log.debug("判题{}完成，result：{}",submit.getId(),run);
                submit.setErrorStatus(run.getStatus()); //记录运行结果信息
                if (run.getStatus() != 0) { //运行失败
                    submit.setTime((int) run.getTime());
                    submit.setMemory((int) run.getMemory());
                    submit.setStatus(4);
                    submit.setErrorMsg(run.getErrorMsg());
                    break;  //不在判题,退出判题
                }else {//运行成功，通过一个
                    submit.setStatus(0);
                    //记录最大运行时间
                    if (submit.getTime()<run.getTime()) {
                        submit.setTime((int) run.getTime());
                    }
//                    System.out.println(submit);
//                    System.out.println(run);
                    if (submit.getMemory()<run.getMemory()) {
                        submit.setMemory((int) run.getMemory());
                    }
                }


            }
            //结果保存
            log.debug("评测完成，保存结果：{}",submit);
            submitService.updateById(submit);
        }finally {
            goJudge.close();
            if(goJudge1!=null)goJudge1.close();
        }

        //竞赛题目，同步到榜单信息
        if(submit.getStatus()!=-1&&MyUtils.isNotEmpty(submit.getContestId())){
            //发送到竞赛记录榜单
            ResponseResult responseResult = contestClient.SendSubmit(submit);
            System.out.println(responseResult);

        }
    }

    //交互判题
    private JudgeResult runSpj(GoJudge goJudge, GoJudge goJudge1, Submit submit, Problem problem, Object input) throws RetrunMsgException, IOException {

        //h获取答案;
        JudgeResult judgeResult = goJudge.privateRun(input, submit.getCode(), problem.getTimeLimit().longValue(),
                problem.getMemoryLimit().longValue(), CompileUnit.get(submit.getLanguage()), null);
        if(judgeResult.getStatus()!=0)return judgeResult; //未运行出结果

        //交互答案
        log.debug("开始运行交互程序");
        JudgeResult judgeResult1 = goJudge1.privateRun(new FileInputStream(judgeResult.getOutput()), problem.getSpjCode(), 5000l, 1024L, CompileUnit.get(problem.getSpjLanguage()), null);
        String inputStream = this.getInputStream(judgeResult1.getOutput());

        log.debug("将交互程序运行结果第一行赋值给结果status：{}",inputStream);
        judgeResult.setStatus(Integer.parseInt(inputStream));  //重新获取status
        return judgeResult;
    }

    public InputStream getFileInput(ProblemCase problemCase) throws FileNotFoundException {
        String dataDir = judgeProperties.getDataDir();
        File file = new File(dataDir, problemCase.getProblemId());
        return new FileInputStream(new File(file, problemCase.getInputFileId()+".in"));

    }

    /**
     * 获取文件的第一行字符串
     * @param file
     * @return
     * @throws IOException
     */
    public String getInputStream(File file) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(file));
        return reader.readLine();
    }

    public InputStream getFileOutput(ProblemCase problemCase) throws FileNotFoundException {
        String dataDir = judgeProperties.getDataDir();
        File file = new File(dataDir, problemCase.getProblemId());
        return new FileInputStream(new File(file, problemCase.getOutputFileId()+".out"));

    }

}
